package com.yeskery.nut.bean;

import com.yeskery.nut.annotation.bean.*;
import com.yeskery.nut.annotation.condition.Conditional;
import com.yeskery.nut.annotation.environment.Value;
import com.yeskery.nut.aop.ProxyType;
import com.yeskery.nut.application.NutApplication;
import com.yeskery.nut.bean.aware.NutApplicationAware;
import com.yeskery.nut.bean.condition.Condition;
import com.yeskery.nut.bean.padding.AutowiredAttributePadding;
import com.yeskery.nut.bean.padding.ResourceAttributePadding;
import com.yeskery.nut.bean.padding.ValueAttributePadding;
import com.yeskery.nut.scan.BeanAnnotationScanMetadata;
import com.yeskery.nut.scan.BeanCreator;
import com.yeskery.nut.scan.bean.ConfigurationBeanCreator;
import com.yeskery.nut.scan.bean.ConfigurationPropertiesBeanCreator;
import com.yeskery.nut.util.BeanUtils;
import com.yeskery.nut.util.ReflectUtils;
import com.yeskery.nut.util.StringUtils;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

/**
 * 基于注解的应用上下文
 * @author sprout
 * 2022-06-20 17:12
 */
public class AnnotationApplicationContext extends BaseApplicationContext {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(AnnotationApplicationContext.class.getName());

    /** Nut应用对象 */
    private final NutApplication nutApplication;

    /** 条件实例缓存map */
    private final Map<Class<?>, Object> conditionInstanceCacheMap = new HashMap<>(16);

    /** 属性填充map */
    private final Map<Class<? extends Annotation>, AttributePadding> attributePaddingMap = new LinkedHashMap<>();

    /** Bean来源构建器 */
    private final Map<BeanAnnotationScanMetadata.Source, BeanCreator> sourceBeanCreatorMap = new HashMap<>();

    /** 延迟注入bean定义列表 */
    private final List<LazyInjectBeanDefinition> lazyInjectBeanDefinitionList = new ArrayList<>();

    /**
     * 构建基于注解的应用上下文
     * @param nutApplication Nut应用对象
     */
    public AnnotationApplicationContext(NutApplication nutApplication) {
        this(nutApplication, null);
    }

    /**
     * 构建基于注解的应用上下文
     * @param nutApplication Nut应用对象
     * @param parentApplicationContext 父级应用上下文
     */
    public AnnotationApplicationContext(NutApplication nutApplication, ApplicationContext parentApplicationContext) {
        super(parentApplicationContext);

        this.nutApplication = nutApplication;
        attributePaddingMap.put(Autowired.class, new AutowiredAttributePadding(this, lazyInjectBeanDefinitionList));
        attributePaddingMap.put(Resource.class, new ResourceAttributePadding(this, lazyInjectBeanDefinitionList));
        attributePaddingMap.put(Value.class, new ValueAttributePadding(nutApplication.getNutConfigure().getEnvironment()));
    }

    @Override
    public void registerBean(Class<?> beanClass) {
        registerBean(beanClass, getBeanAnnotationScanMetadata());
    }

    /**
     * 注册bean对象
     * @param beanClass bean类型对象
     * @param beanAnnotationScanMetadata 所有Bean扫描元数据类型
     */
    public void registerBean(Class<?> beanClass, Collection<BeanAnnotationScanMetadata> beanAnnotationScanMetadata) {
        if (!isMatchConditionBean(beanClass, beanAnnotationScanMetadata)) {
            return;
        }
        if (!isComponentAnnotationPresent(beanClass)) {
            beanAnnotationScanMetadata.stream()
                    .filter(m -> m.getType().equals(beanClass) || m.getSuperClasses().contains(beanClass) || m.getInterfaceClasses().contains(beanClass))
                    .filter(m -> !containBean(m.getType()))
                    .forEach(this::registerBean);
            return;
        }
        BeanDefinition beanDefinition = getBeanDefinition(beanClass);
        if (beanDefinition == null) {
            throw new BeanException("Bean Definition Not Found.");
        }
        registerBean(beanDefinition);
    }

    @Override
    public void registerBean(BeanAnnotationScanMetadata beanAnnotationMetadata) {
        BeanAnnotationScanMetadata.Source source = beanAnnotationMetadata.getSource();
        if (source == BeanAnnotationScanMetadata.Source.COMPONENT) {
            super.registerBean(beanAnnotationMetadata);
            return;
        }
        BeanCreator beanCreator = sourceBeanCreatorMap.get(source);
        if (beanCreator == null) {
            if (source == BeanAnnotationScanMetadata.Source.CONFIGURATION_PROPERTIES) {
                beanCreator = new ConfigurationPropertiesBeanCreator(this, nutApplication.getNutConfigure().getEnvironment());
            } else if (source == BeanAnnotationScanMetadata.Source.CONFIGURATION) {
                beanCreator = new ConfigurationBeanCreator(this, this, getBeanAnnotationScanMetadata());
            } else {
                throw new BeanException("UnSupport Bean Source [" + source + "]");
            }
            sourceBeanCreatorMap.put(source, beanCreator);
        }
        beanCreator.createBean(beanAnnotationMetadata);
    }

    /**
     * 条件判断bean是否匹配
     * @param beanClass bean类型
     * @param beanMetadataCollection bean元数据集合
     * @return 条件判断bean是否匹配
     */
    public boolean isMatchConditionBean(Class<?> beanClass, Collection<BeanAnnotationScanMetadata> beanMetadataCollection) {
        return doIsMatchConditionBean(beanClass, beanClass.getAnnotations(), beanMetadataCollection);
    }

    /**
     * 条件判断bean是否匹配
     * @param method 方法对象
     * @param beanMetadataCollection bean元数据集合
     * @return 条件判断bean是否匹配
     */
    public boolean isMatchConditionBean(Method method, Collection<BeanAnnotationScanMetadata> beanMetadataCollection) {
        return doIsMatchConditionBean(method.getReturnType(), method.getAnnotations(), beanMetadataCollection);
    }

    /**
     * 获取bean定义信息
     * @param clazz bean类型
     * @return bean定义信息
     */
    @Override
    protected BeanDefinition getBeanDefinition(Class<?> clazz) {
        Stack<Class<? extends Annotation>> stack = BeanUtils.findReverseSpecifiedAnnotationStack(clazz, Component.class);
        List<Annotation> componentAnnotations = new LinkedList<>();
        Class<?> type = clazz;
        while (!stack.isEmpty()) {
            Class<? extends Annotation> annotationType = stack.pop();
            Annotation annotation = type.getAnnotation(annotationType);
            if (annotation == null) {
                throw new BeanException("Create Class[" + clazz.getName() + "] BeanDefinition Fail, Can Not Found Annotation["
                        + annotationType.getName() + "] From Type[" + type.getName() + "].");
            }
            componentAnnotations.add(annotation);
            type = annotation.annotationType();
        }

        Stack<Annotation> componentAnnotationsStack = new Stack<>();
        for (Annotation componentAnnotation : componentAnnotations) {
            componentAnnotationsStack.push(componentAnnotation);
        }

        String beanName = "";
        String scope = ApplicationContext.SCOPE_SINGLETON;
        ProxyType proxyType = ProxyType.AUTO;
        Class<?>[] emptyClasses = new Class[0];
        while (!componentAnnotationsStack.isEmpty()) {
            Annotation annotation = componentAnnotationsStack.pop();
            Method valueMethod = ReflectUtils.findSpecifiedMethod(annotation.annotationType(), "value",
                    emptyClasses, String.class);
            if (valueMethod != null) {
                try {
                    beanName = (String) valueMethod.invoke(annotation);
                } catch (Exception e) {
                    throw new BeanException("Create Class[" + clazz.getName()
                            + "] BeanDefinition Fail, Can Not Execute Method[java.lang.String value()]"
                            + " From Annotation[" + annotation.annotationType().getName() + "].");
                }
            }

            Method scopeMethod = ReflectUtils.findSpecifiedMethod(annotation.annotationType(), "scope",
                    emptyClasses, String.class);
            if (scopeMethod != null) {
                try {
                    scope = (String) scopeMethod.invoke(annotation);
                } catch (Exception e) {
                    throw new BeanException("Create Class[" + clazz.getName()
                            + "] BeanDefinition Fail, Can Not Execute Method[java.lang.String scope()]"
                            + " From Annotation[" + annotation.annotationType().getName() + "].");
                }
            }

            Method proxyTypeMethod = ReflectUtils.findSpecifiedMethod(annotation.annotationType(), "proxyType",
                    emptyClasses, ProxyType.class);
            if (proxyTypeMethod != null) {
                try {
                    proxyType = (ProxyType) proxyTypeMethod.invoke(annotation);
                } catch (Exception e) {
                    throw new BeanException("Create Class[" + clazz.getName()
                            + "] BeanDefinition Fail, Can Not Execute Method[com.yeskery.nut.aop.ProxyType proxyType()]"
                            + " From Annotation[" + annotation.annotationType().getName() + "].");
                }
            }
        }

        return getComponentBeanDefinition(clazz, beanName, ApplicationContext.SCOPE_SINGLETON.equals(scope),
                clazz.isAnnotationPresent(Primary.class), proxyType);
    }

    @Override
    public void beanAttributePadding(Object object) {
        Set<Field> paddingFields = new HashSet<>();
        for (Map.Entry<Class<? extends Annotation>, AttributePadding> entry : attributePaddingMap.entrySet()) {
            Field[] dependsFields = ReflectUtils.getBeanAnnotationField(object.getClass(), entry.getKey());
            dependsFields = Arrays.stream(dependsFields).filter(f -> paddingFields.stream().noneMatch(x -> x.equals(f))).toArray(Field[]::new);
            entry.getValue().padding(object, dependsFields);
            paddingFields.addAll(Arrays.stream(dependsFields).collect(Collectors.toList()));
        }
    }

    @Override
    protected void executeExtendBeanAwareInterfaceMethod(Object bean, BeanDefinition beanDefinition) {
        if (bean instanceof NutApplicationAware) {
            ((NutApplicationAware) bean).setNutApplication(nutApplication);
        }
    }

    /**
     * 获取Component注解的定义对象
     * @param clazz 类对象
     * @param beanName bean名称
     * @param singleton 是否单例
     * @param primary 是否优先级bean
     * @param proxyType 代理类型
     * @return Component注解的定义对象
     */
    protected BeanDefinition getComponentBeanDefinition(Class<?> clazz, String beanName, boolean singleton,
                                                        boolean primary, ProxyType proxyType) {
        BeanDefinition beanDefinition = new BeanDefinition();
        if (StringUtils.isEmpty(beanName)) {
            beanName = ReflectUtils.getDefaultBeanName(clazz);
        }
        beanDefinition.setBeanName(beanName);
        beanDefinition.setBeanClass(clazz);
        beanDefinition.setSingleton(singleton);
        beanDefinition.setPrimary(primary);
        beanDefinition.setProxy(getProxyObjectContext().shouldProxy(clazz));
        Lazy lazy = clazz.getAnnotation(Lazy.class);
        beanDefinition.setLazy(lazy != null && lazy.value());
        beanDefinition.setLazyInject(clazz.isAnnotationPresent(LazyInject.class));
        beanDefinition.setProxyType(proxyType);
        beanDefinition.setConstructor(getBeanFitConstructor(clazz));
        beanDefinition.setDepends(getBeanDepends(clazz));
        return beanDefinition;
    }

    @Override
    protected void executeAfterApplicationInitialized() {
        for (LazyInjectBeanDefinition lazyInjectBeanDefinition : lazyInjectBeanDefinitionList) {
            try {
                Object value = lazyInjectBeanDefinition.getLazyInjectSupplier().get();
                if (value != null) {
                    ReflectUtils.setObjectFieldValue(lazyInjectBeanDefinition.getBean(), lazyInjectBeanDefinition.getField(), value);
                }
            } catch (Exception e) {
                Field field = lazyInjectBeanDefinition.getField();
                throw new BeanException("Bean's Attribute [ " + field.getType().getName() + "] Name [" + field.getName() + "] Padding Fail.", e);
            }
        }
        lazyInjectBeanDefinitionList.clear();
        super.executeAfterApplicationInitialized();
    }

    /**
     * 获取bean依赖
     * @param beanClass bean类型
     * @return bean依赖
     */
    private BeanAttribute[] getBeanDepends(Class<?> beanClass) {
        Field[] autowiredDependsFields = getAutowiredBeanDependsField(beanClass);
        Field[] resourceDependsFields = getResourceBeanDependsField(beanClass);
        BeanAttribute[] beanAttributes = new BeanAttribute[autowiredDependsFields.length + resourceDependsFields.length];
        int i = 0;
        for (; i < autowiredDependsFields.length; i++) {
            Field field = autowiredDependsFields[i];
            Lazy lazy = field.getAnnotation(Lazy.class);
            if ((lazy != null && lazy.value()) || field.isAnnotationPresent(LazyInject.class)) {
                beanAttributes[i] = null;
                continue;
            }
            Autowired autowired = field.getAnnotation(Autowired.class);
            Qualifier qualifier = field.getAnnotation(Qualifier.class);
            BeanAttribute beanAttribute = new BeanAttribute();
            beanAttribute.setRequired(autowired.required());
            beanAttribute.setType(BeanUtils.getDependBeanClass(beanClass, field.getGenericType()));
            String beanName = qualifier == null ? null : StringUtils.isEmpty(qualifier.value()) ? null : qualifier.value();
            beanAttribute.setName(beanName);
            beanAttributes[i] = beanAttribute;
        }
        for (Field field : resourceDependsFields) {
            Lazy lazy = field.getAnnotation(Lazy.class);
            if ((lazy != null && lazy.value()) || field.isAnnotationPresent(LazyInject.class)) {
                beanAttributes[i++] = null;
                continue;
            }
            Resource resource = field.getAnnotation(Resource.class);
            BeanAttribute beanAttribute = new BeanAttribute();
            beanAttribute.setRequired(false);
            beanAttribute.setType(BeanUtils.getDependBeanClass(beanClass, field.getGenericType()));
            beanAttribute.setName(StringUtils.isEmpty(resource.name()) ? null : resource.name());
            beanAttributes[i++] = beanAttribute;
        }
        return Arrays.stream(beanAttributes).filter(Objects::nonNull).toArray(BeanAttribute[]::new);
    }

    /**
     * 获取Autowired注解bean依赖属性对象
     * @param beanClass bean类型
     * @return bean依赖属性对象
     */
    private Field[] getAutowiredBeanDependsField(Class<?> beanClass) {
        return ReflectUtils.getBeanAnnotationField(beanClass, Autowired.class);
    }

    /**
     * 获取Resource注解bean依赖属性对象
     * @param beanClass bean类型
     * @return bean依赖属性对象
     */
    private Field[] getResourceBeanDependsField(Class<?> beanClass) {
        return ReflectUtils.getBeanAnnotationField(beanClass, Resource.class);
    }

    /**
     * 获取是否是Component注解的派生注解
     * @param beanClass bean类型
     * @return 是否是Component注解的派生注解
     */
    private boolean isComponentAnnotationPresent(Class<?> beanClass) {
        return BeanUtils.isExistSpecifiedAnnotation(beanClass, Component.class);
    }

    /**
     * 条件判断bean是否匹配
     * @param beanClass bean类型
     * @param annotations 注解数组
     * @param beanMetadataCollection bean元数据集合
     * @return 条件判断bean是否匹配
     */
    private boolean doIsMatchConditionBean(Class<?> beanClass, Annotation[] annotations, Collection<BeanAnnotationScanMetadata> beanMetadataCollection) {
        beanMetadataCollection = beanMetadataCollection.stream()
                .filter(m -> !m.getType().equals(beanClass))
                .filter(m -> !m.getSuperClasses().contains(beanClass) && !m.getInterfaceClasses().contains(beanClass))
                .collect(Collectors.toList());
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> type = annotation.annotationType();
            if (type.equals(Conditional.class)) {
                if (doConditionalAnnotationNotMatch(annotation, (Conditional) annotation, beanClass, beanMetadataCollection)) {
                    return false;
                }
            } else {
                if (doConditionalAnnotationNotMatch(annotation, type.getAnnotation(Conditional.class), beanClass, beanMetadataCollection)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 条件判断注解是否不匹配
     * @param annotation 注解对象
     * @param conditional 条件装配注解对象
     * @param beanClass bean类型
     * @param beanMetadataCollection bean元数据集合
     * @return 条件判断注解是否不匹配
     */
    private boolean doConditionalAnnotationNotMatch(Annotation annotation, Conditional conditional, Class<?> beanClass,
                                                    Collection<BeanAnnotationScanMetadata> beanMetadataCollection) {
        if (conditional != null) {
            for (Class<? extends Condition> conditionClass : conditional.value()) {
                Condition condition = (Condition) ReflectUtils.getNoConstructorTarget(conditionInstanceCacheMap, conditionClass);
                if (!condition.matches(nutApplication, beanMetadataCollection, annotation)) {
                    if (logger.isLoggable(Level.FINEST)) {
                        logger.log(Level.FINEST, "Bean Inject Verify Not Match, Bean Class[" + beanClass.getName()
                                + "], Conditional[" + annotation + "], Condition[" + condition.getClass().getName() + "]");
                    }
                    return true;
                }
            }
        }
        return false;
    }
}
